/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.factory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.javaruntype.type.Type;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGParameterList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.collection.ParameterNameList;
import org.jfab.core.model.CGAnnotation;
import org.jfab.core.model.CGParameter;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides a string factory.
 */
public final class DefaultStringFactory implements StringFactory
{
    /** Logger. */
    private static final Logger LOGGER = LoggerFactory
            .getLogger(DefaultStringFactory.class.getName());

    /** Member variable prefix. */
    private String _memberVariablePrefix = "_";

    /** Type reference support. */
    private final TypeReferenceSupport _typeReferenceSupport;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /**
     * Construct this object with the given parameters.
     * 
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param typeReferenceSupport Type reference support. (required)
     */
    public DefaultStringFactory(final CGTypeUtilities cgTypeUtils,
            final TypeReferenceSupport typeReferenceSupport)
    {
        Validate.notNull(cgTypeUtils, "cgTypeUtils is null");
        Validate.notNull(typeReferenceSupport, "typeReferenceSupport is null");

        _cgTypeUtils = cgTypeUtils;
        _typeReferenceSupport = typeReferenceSupport;
    }

    @Override
    public void clear()
    {
        getTypeReferenceSupport().clear();
    }

    @Override
    public final String convertFirstCharacterToLowerCase(final String value)
    {
        String answer = value;

        if ((value != null) && (value.length() > 0))
        {
            final Character ch = value.charAt(0);
            answer = Character.toLowerCase(ch) + value.substring(1);
        }

        return answer;
    }

    @Override
    public final String convertFirstCharacterToUpperCase(final String value)
    {
        String answer = value;

        if ((value != null) && (value.length() > 0))
        {
            final Character ch = value.charAt(0);
            answer = Character.toUpperCase(ch) + value.substring(1);
        }

        return answer;
    }

    @Override
    public final String convertPropertyName(final String propertyName)
    {
        String answer = null;

        if (propertyName != null)
        {
            if (propertyName.length() == 1)
            {
                answer = propertyName.toUpperCase();
            }
            else if (propertyName.length() > 1)
            {
                final char ch = propertyName.charAt(0);
                answer = Character.toUpperCase(ch) + propertyName.substring(1);
            }
        }

        return answer;
    }

    @Override
    public final String convertPropertyNameToStaticName(
            final String propertyName)
    {
        String answer = null;

        if ((propertyName != null) && (propertyName.length() > 1))
        {
            final StringBuilder sb = new StringBuilder();
            final int size = propertyName.length();

            for (int i = 0; i < size; i++)
            {
                final Character ch = propertyName.charAt(i);

                if (Character.isLowerCase(ch))
                {
                    sb.append(Character.toUpperCase(ch));
                }
                else
                {
                    sb.append("_").append(ch);
                }
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createAccessorNameFor(final CGType propertyType,
            final String propertyName)
    {
        Validate.notNull(propertyType,
                "propertyType is null for propertyName = " + propertyName);

        String answer = null;

        if (StringUtils.isNotEmpty(propertyName))
        {
            String methodPrefix = "get";

            final Type<?> type = _cgTypeUtils.getType(propertyType);

            if (type != null)
            {
                final Class<?> rawClass = type.getRawClass();

                if ((rawClass == boolean.class) || (rawClass == Boolean.class))
                {
                    methodPrefix = "is";
                }
            }

            final String methodSuffix = convertPropertyName(propertyName);
            answer = methodPrefix + methodSuffix;
        }

        return answer;
    }

    @Override
    public final String createAnnotationsString(
            final CGAnnotationList annotations)
    {
        String answer = "";

        try
        {
            final Object helperObject = this;
            final Method helperMethod = getClass().getMethod(
                    "createAnnotationString", CGAnnotation.class);
            answer = createStringFromList("", "@", annotations, " ",
                    helperObject, helperMethod);

            if (answer.length() > 0)
            {
                answer += " ";
            }
        }
        catch (final SecurityException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }
        catch (final NoSuchMethodException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }

        return answer;
    }

    @Override
    public final String createAnnotationString(final CGAnnotation annotation)
    {
        final CGType type = annotation.getType();

        final StringBuilder sb = new StringBuilder();

        sb.append(createTypeString(type));

        final ParameterNameList attributes = new ParameterNameList(
                annotation.getAttributes());

        if (CollectionUtils.isNotEmpty(attributes))
        {
            sb.append("(");

            final int size = attributes.size();

            for (int i = 0; i < size; i++)
            {
                final String attribute = attributes.get(i);
                sb.append(attribute);

                if (i < (size - 1))
                {
                    sb.append(", ");
                }
            }

            sb.append(")");
        }

        return sb.toString();
    }

    @Override
    public final String createArgumentsString(final ParameterNameList args)
    {
        String answer = "";

        if (CollectionUtils.isNotEmpty(args))
        {
            final StringBuilder sb = new StringBuilder();
            final int size = args.size();

            for (int i = 0; i < size; i++)
            {
                sb.append(args.get(i));

                if (i < (size - 1))
                {
                    sb.append(", ");
                }
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createCommentFromPropertyName(final String propertyName)
    {
        String answer = null;

        if (!StringUtils.isEmpty(propertyName))
        {
            final StringBuilder sb = new StringBuilder();

            Character ch = propertyName.charAt(0);
            sb.append(Character.toUpperCase(ch));

            final int size = propertyName.length();

            for (int i = 1; i < size; i++)
            {
                ch = propertyName.charAt(i);

                if (Character.isUpperCase(ch))
                {
                    sb.append(" ").append(Character.toLowerCase(ch));
                }
                else
                {
                    sb.append(ch);
                }
            }

            sb.append(".");

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createCreateNameFor(final String propertyName)
    {
        String answer = null;

        if (StringUtils.isNotEmpty(propertyName))
        {
            final String methodPrefix = "create";
            final String methodSuffix = convertPropertyName(propertyName);
            answer = methodPrefix + methodSuffix;
        }

        return answer;
    }

    @Override
    public final String createExtendsString(final CGType extendsType)
    {
        String answer = "";

        if (extendsType != null)
        {
            final String typeString = createTypeString(extendsType);
            answer = " extends " + typeString;
        }

        return answer;
    }

    @Override
    public final String createExtendsString(final CGTypeList superClasses)
    {
        String answer = "";

        if (CollectionUtils.isNotEmpty(superClasses))
        {
            final StringBuilder sb = new StringBuilder(" extends ");

            final int size = superClasses.size();

            for (int i = 0; i < size; i++)
            {
                final CGType superClass = superClasses.get(i);
                final String superClassName = createTypeString(superClass);
                sb.append(superClassName);

                if (i < (size - 1))
                {
                    sb.append(", ");
                }
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createImplementsString(
            final CGTypeList implementsInterfaces)
    {
        String answer = "";

        try
        {
            final Object helperObject = getTypeReferenceSupport();
            final Method helperMethod = helperObject.getClass().getMethod(
                    "createTypeString", CGType.class);
            answer = createStringFromList(" implements ", "",
                    implementsInterfaces, ", ", helperObject, helperMethod);
        }
        catch (final SecurityException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }
        catch (final NoSuchMethodException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }

        return answer;
    }

    @Override
    public final String createInitialValueString(final String initialValue)
    {
        String answer = "";

        if (!StringUtils.isEmpty(initialValue))
        {
            answer = " = " + initialValue;
        }

        return answer;
    }

    @Override
    public final String createMemberVariableName(final String propertyName)
    {
        String answer = propertyName;

        if (!propertyName.startsWith(getMemberVariablePrefix()))
        {
            answer = getMemberVariablePrefix() + propertyName;
        }

        return answer;
    }

    @Override
    public final String createModifiersString(final ModifierList modifiers)
    {
        String answer = "";

        if (modifiers != null)
        {
            final StringBuilder sb = new StringBuilder();

            for (final String modifier : modifiers)
            {
                sb.append(modifier).append(" ");
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createMutatorNameFor(final String propertyName)
    {
        String answer = null;

        if (StringUtils.isNotEmpty(propertyName))
        {
            answer = "set" + convertPropertyName(propertyName);
        }

        return answer;
    }

    @Override
    public final String createParametersString(final CGParameterList parameters)
    {
        Validate.notNull(parameters, "parameters is null");

        String answer = "";

        if (CollectionUtils.isNotEmpty(parameters))
        {
            final StringBuilder sb = new StringBuilder();
            final int size = parameters.size();

            for (int i = 0; i < size; i++)
            {
                final CGParameter cgParameter = parameters.get(i);
                sb.append(createTypeString(cgParameter.getType()));
                sb.append(" ");
                sb.append(cgParameter.getName());

                if (i < (size - 1))
                {
                    sb.append(", ");
                }
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createPropertyChangeName(final String propertyName)
    {
        return convertPropertyNameToStaticName(propertyName) + "_PROPERTY";
    }

    @Override
    public String createPropertyName(final String memberVariableName)
    {
        String answer = memberVariableName;

        if (memberVariableName.startsWith(getMemberVariablePrefix()))
        {
            answer = memberVariableName.substring(getMemberVariablePrefix()
                    .length());
        }

        return answer;
    }

    @Override
    public final String createReturnTypeString(final CGType returnType)
    {
        String answer = "void";

        if (returnType != null)
        {
            answer = getTypeReferenceSupport().createTypeString(returnType);
            answer = answer.replaceAll("\\<\\?\\>", "");
        }

        return answer;
    }

    @Override
    public final <T> String createStringFromList(final String prefix,
            final String itemPrefix, final List<T> list,
            final String separator, final Object helperObject,
            final Method helperMethod)
    {
        String answer = "";

        if (CollectionUtils.isNotEmpty(list))
        {
            final StringBuilder sb = new StringBuilder();
            sb.append(prefix);

            final int size = list.size();

            for (int i = 0; i < size; i++)
            {
                final T item = list.get(i);

                try
                {
                    sb.append(itemPrefix);

                    final String itemString = (String)helperMethod.invoke(
                            helperObject, item);
                    sb.append(itemString);
                }
                catch (final IllegalArgumentException e)
                {
                    if (LOGGER.isDebugEnabled())
                    {
                        LOGGER.debug(e.getMessage(), e);
                    }
                }
                catch (final IllegalAccessException e)
                {
                    if (LOGGER.isDebugEnabled())
                    {
                        LOGGER.debug(e.getMessage(), e);
                    }
                }
                catch (final InvocationTargetException e)
                {
                    if (LOGGER.isDebugEnabled())
                    {
                        LOGGER.debug(e.getMessage(), e);
                    }
                }

                if (i < (size - 1))
                {
                    sb.append(separator);
                }
            }

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String createThrowsString(final CGTypeList throwsTypes)
    {
        String answer = "";

        try
        {
            final Object helperObject = getTypeReferenceSupport();
            final Method helperMethod = helperObject.getClass().getMethod(
                    "createTypeString", CGType.class);
            answer = createStringFromList(" throws ", "", throwsTypes, ", ",
                    helperObject, helperMethod);
        }
        catch (final SecurityException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }
        catch (final NoSuchMethodException e)
        {
            if (LOGGER.isDebugEnabled())
            {
                LOGGER.debug(e.getMessage(), e);
            }
        }

        return answer;
    }

    @Override
    public String createTypeParametersString(final List<String> typeParameters)
    {
        String answer = "";

        if (CollectionUtils.isNotEmpty(typeParameters))
        {
            final StringBuilder sb = new StringBuilder("<");

            final int size = typeParameters.size();

            for (int i = 0; i < size; i++)
            {
                final String typeParameter = typeParameters.get(i);
                sb.append(typeParameter);

                if (i < (size - 1))
                {
                    sb.append(", ");
                }
            }

            sb.append(">");

            answer = sb.toString();
        }

        return answer;
    }

    @Override
    public final String getMemberVariablePrefix()
    {
        return _memberVariablePrefix;
    }

    /**
     * @return the typeReferenceSupport
     */
    @Override
    public TypeReferenceSupport getTypeReferenceSupport()
    {
        return _typeReferenceSupport;
    }

    @Override
    public final void setMemberVariablePrefix(final String memberVariablePrefix)
    {
        _memberVariablePrefix = memberVariablePrefix;
    }

    /**
     * @param cgType Code generator type.
     * 
     * @return a new type string.
     */
    private String createTypeString(final CGType cgType)
    {
        String answer = null;

        if (cgType != null)
        {
            final String packageName = cgType.getPackageName();

            if (StringUtils.isNotEmpty(packageName))
            {
                getTypeReferenceSupport().setPackageName(packageName);
            }

            answer = getTypeReferenceSupport().createTypeString(cgType);
        }

        return answer;
    }
}
